/**
 * Represents an Index and stores any necessary information
 * to identify the index across the whole system.
 *
 * An index is defined by a name and a set of attribute types that form the type of the key.
 * It can hold an unlimited amount of records as long as they are compatible with that key type.
 *
 */

#include <cstdlib>
#include <map>
#include <cstring>
#include <pthread.h>

#include "../include/contest_interface.h"
#include "../include/Index.h"
#include "../include/Iterator.h"
#include "../include/tx.h"
#include "../include/Mutex.h"
#include "../include/util.h"
#include "../include/IndexSchema.h"
#include "../include/QuadTree.h"

pthread_once_t map_indexes::once_ = PTHREAD_ONCE_INIT;
map_indexes* map_indexes::single;

map_indexes& map_indexes::getInstance() {
    pthread_once(&once_, &Initialize);
    return *single;
}

void map_indexes::Initialize() {
    single = new map_indexes();
    atexit(&Destroy);
}

// Destroy the singleton instance of IndexManager

void map_indexes::Destroy() {
    delete single;
    single = 0;
}

IndexSchema* map_indexes::Find(const char* name) {
    uint32_t length = strlen(name) + 1;
    char* temp = new char[length];
    strcpy(temp, name);
    std::string myname = temp;

    lock(mutex_) {
        std::map< string, IndexSchema*>::iterator it = mymap.find(myname);
        if (mymap.size() == 0)
            return NULL;

        if (it != mymap.end()) {

            return it->second;
        }
        return NULL;
    }
    //TODO return outside of lock
}

bool map_indexes::test_existance(const char* name) {
    //    cout << "I'm here 2.1"<<endl;
    if (Find(name) == NULL) {
        return false;
    }
    return true;
}

ErrorCode map_indexes::insert(const char* name, IndexSchema * ix) {
    char* temp = new char[strlen(name) + 1];
    strcpy(temp, name);
    std::string myname = temp;

    lock(mutex_) {
        mymap[myname] = ix;
    }
    //TODO return
}

/**
 * @return ErrorCode
 * - \ref kOk
 *     -->   if the index was successfully deleted
 * - \ref kErrorUnknownIndex
 *     -->   if an index with the given name does not exist
 * - \ref kErrorOpenTransactions
 *     -->   if the index could not be deleted because it has been modified by transactions
 *        that are still open
 * - \ref kErrorGenericFailure
 *     -->   if the index could not be deleted for some other reason
 */
ErrorCode map_indexes::deleteIndex(const char* name) {
    uint32_t length = strlen(name) + 1;
    char* temp = new char[length];
    strcpy(temp, name);
    std::string myname = temp;

    lock(mutex_) {
        try {
            std::map< string, IndexSchema*>::iterator it = mymap.find(myname);
            if (it != mymap.end()) {
                if (it->second->is_tx_empty()) { //List of Transaction if it's empty or not
                    it->second->CloseHandles();

                    mymap.erase(it);
                } else {
                    return kErrorOpenTransactions;
                }
            } else {
                return kErrorUnknownIndex;
            }
        } catch (exception &e) {
            return kErrorGenericFailure;
        }
    }
    return kOk;
}

Index::Index(IndexSchema *schema_) {
    this->schema = schema_;
    this->closeFlag = false;
}

void Index::Close() {

    lock(mutex_) {
        if (!closeFlag) {
            closeFlag = true;
        } else
            return;

        if (this->getSchema() != NULL) {
            this->getSchema()->UnregisterHandle(this);
        }

    }
}

//////////////////////////////////////////
// Functions used to connect with QTree //
//////////////////////////////////////////

ErrorCode Index::Insert(Transaction *tx, Record * _record) {
    try {
        return this->schema->get_QuadTree()->can_InsertRecord(_record, tx);
    } catch (ErrorCode &e) {
        return e;
    } catch (...) {
        return kErrorGenericFailure;
    }

}

ErrorCode Index::DeleteRecord(Transaction *tx, Record *r, uint8_t duplicateFlag, Iterator* it) {
    try {
        ErrorCode ret;

        //        lock(mutex_) {
        ret = this->schema->get_QuadTree()->can_ModifyRecord(r, tx, duplicateFlag, &it);
        //TODO
        //        }
        if (ret != kOk) {
            return ret;
        }
    } catch (ErrorCode &e) {
        return e;
    } catch (...) {
        return kErrorGenericFailure;
    }
    return kOk;
}

ErrorCode Index::UpdateRecord(Transaction *tx, Record *record, uint8_t flags, Iterator* it) {
    try {
        return this->getSchema()->get_QuadTree()->can_ModifyRecord(record, tx, flags, &it);
    } catch (ErrorCode &e) {
        return e;
    } catch (...) {
        return kErrorGenericFailure;
    }
    return kOk;
}

ErrorCode Index::Search(Transaction *tx, Key min, Key max, Iterator ** it) {
    bool partial = false;
    ErrorCode ret;
    try {
        for (int i = 0; i < min.attribute_count; i++) {
            if ((min.value[i] == NULL) || (max.value[i] == NULL)) {
                partial = true;
                break;
            }
        }

        if (partial) {
            int att_count_null = 0;
            std::vector< int> *ls = new vector< int>();
            for (int i = 0; i < min.attribute_count; i++) {
                if (min.value[i] == NULL) {
                    ls->push_back(i);
                    att_count_null += 1;
                }
            }

            //            lock(mutex_){
            ret = this->getSchema()->get_QuadTree()->PartialSearch(min, it, ls, att_count_null);
            //            }
            return ret;
        } else {
            if (keycmp(min, max) == 0) {

                //            lock(mutex_){
                ret = this->getSchema()->get_QuadTree()->PointSearch(min, it);
                //            }
                return ret;
            } else {

                //            lock(mutex_){
                ret = this->getSchema()->get_QuadTree()->RangeSearch(min, max, it);
                //            }
                return ret;
            }
        }
    } catch (ErrorCode &e) {
        return e;
    } catch (...) {
        return kErrorGenericFailure;
    }
    return kOk;
}
